﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace LocalBeamSearchDemo
{
    public partial class MainPage : UserControl
    {
        public int K { get; set; }
        private List<Point> SurfacePts = new List<Point>();
        private List<SearchPoint> CurPoints = new List<SearchPoint>();

        Storyboard timer = new Storyboard();

        public MainPage()
        {
            InitializeComponent();

            K = 4;

            timer.Completed += new EventHandler(OnTick);
            timer.Duration = TimeSpan.FromMilliseconds(500);

            SetupSurface();

            PathFigure figure = new PathFigure();
            figure.StartPoint = SurfacePts[0];

            foreach (Point p in SurfacePts)
            {
                LineSegment seg = new LineSegment();
                seg.Point = p;

                figure.Segments.Add(seg);
            }

            Surface.Figures.Add(figure);
        }

        void OnTick(object sender, EventArgs e)
        {
            //if all points same, stop and return
            if (HavePointsConverged())
            {
                //timer.Stop();
                //return;
            }

            List<int> successors = new List<int>();

            foreach (SearchPoint p in CurPoints)
            {
                int prevPt = (p.Index > 0) ? p.Index - 1 : p.Index;
                int nextPt = (p.Index < SurfacePts.Count - 1) ? p.Index + 1 : p.Index;

                //cur point is always possible successor
                successors.Add(p.Index);
                successors.Add(prevPt);
                successors.Add(nextPt);
            }

            //remove duplicate entries
            IEnumerable<int> successorsDistinct = successors.Distinct();

            IEnumerable<int> sorted = (IEnumerable<int>)successorsDistinct.OrderBy((int i) => SurfacePts[i].Y);
            List<int> weighted = new List<int>();

            int curPos = 0;
            foreach (int option in sorted)
            {
                int weight = 1;

                //this option is in top 4th of choices
                if (curPos < (sorted.Count() / 4))
                {
                    weight = 10;
                }
                else if (curPos < (sorted.Count() / 2))
                {
                    weight = 6;
                }
                else if (curPos < (sorted.Count() - (sorted.Count() / 4)))
                {
                    //option is in 3rd 4th of choices
                    weight = 3;
                }


                for (int j = 0; j < weight; j++)
                {
                    weighted.Add(option);
                }

                curPos++;
            }

            List<int> picks = new List<int>();
            picks.Add(sorted.ElementAt(0));//always choose best option
            Random rand = new Random(DateTime.Now.Millisecond);
            for (int j = 0; j < K - 1; j++)
            {
                int index = rand.Next(weighted.Count);
                picks.Add(weighted[index]);
            }

            //IEnumerable<int> picks = (IEnumerable<int>)successors.OrderBy((int i) => SurfacePts[i].Y).Take(K);
            UpdateCurPoints(picks);

            timer.Begin();
        }

        private bool HavePointsConverged()
        {
            bool bReturn = true;
            int val = -1;

            foreach (SearchPoint p in CurPoints)
            {
                if (val == -1)
                {
                    val = p.Index;
                }

                bReturn &= val == p.Index;
            }

            return bReturn;
        }

        public void InitiateSearch()
        {
            timer.Stop();

            Random rand = new Random(DateTime.Now.Millisecond);
            List<int> indexes = new List<int>();

            for (int i = 0; i < K; i++)
            {
                int index = rand.Next(SurfacePts.Count);
                indexes.Add(index);
            }

            UpdateCurPoints(indexes);
        }


        private void SetupSurface()
        {
            double start = -4d;
            double end = 0.6d;
            double length = end - start;

            double x = start;
            double step = length / MainCanvas.Width;

            //create our surface
            while (x < end)
            {
                double y = 0;

                //from -4.25 to 0.6
                //2 * (sin(pi*x)^5)-(x^2 + (4 * x)) + 2 * sin(x)

                y = 2 * Math.Pow(Math.Sin(Math.PI * x), 5) - (Math.Pow(x, 2) + (4 * x)) + 2 * Math.Sin(x);

                SurfacePts.Add(new Point(x, y));

                x += step;
            }

            //do transforms
            for (int i = 0; i < SurfacePts.Count; i++)
            {
                Point curPoint = SurfacePts[i];

                //translate right
                curPoint.X += (-1 * start);

                //scale graph
                curPoint.X /= step;
                curPoint.Y *= -50;

                //translate down
                curPoint.Y += (MainCanvas.Height - 200) / 1.5d;

                SurfacePts[i] = curPoint;
            }

            Random rand = new Random(DateTime.Now.Millisecond);
            double noiseAmt = 20d;

            for (int i = 1; i < SurfacePts.Count - 5; i+=6)
            {
                double amt1 = (rand.NextDouble() - .5d) * noiseAmt;
                double amt2 = amt1 * .7d;
                //double amt3 

                SurfacePts[i + 1] = new Point(SurfacePts[i + 1].X, SurfacePts[i + 1].Y + amt2);
                SurfacePts[i + 2] = new Point(SurfacePts[i + 2].X, SurfacePts[i + 2].Y + amt1);
                SurfacePts[i + 3] = new Point(SurfacePts[i + 3].X, SurfacePts[i + 3].Y + amt1);
                SurfacePts[i + 4] = new Point(SurfacePts[i + 4].X, SurfacePts[i + 4].Y + amt2);
            }
        }

        private void Initiate_Click(object sender, RoutedEventArgs e)
        {
            K = (int)NumSlider.Value;
            InitiateSearch();
            timer.Begin();
        }

        private void UpdateCurPoints(IEnumerable<int> newIndicies)
        {
            foreach (SearchPoint p in CurPoints)
            {
                MainCanvas.Children.Remove(p.Path);
            }

            CurPoints.Clear();

            foreach (int index in newIndicies)
            {
                Point p = SurfacePts[index];

                Path path = new Path()
                {
                    Fill = new SolidColorBrush(Colors.Red),
                    Data = new EllipseGeometry()
                    {
                        Center = p,
                        RadiusX = 3,
                        RadiusY = 3
                    }
                };

                MainCanvas.Children.Add(path);

                CurPoints.Add(new SearchPoint()
                {
                    Path = path,
                    Index = index
                });
            }
        }

        private void NumSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (NumBeams != null)
            {
                NumBeams.Text = ((int)NumSlider.Value).ToString();
            }
        }
    }

    public class SearchPoint
    {
        public Path Path { get; set; }
        public int Index { get; set; }
    }
}
